﻿#region Namespace References

using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using DynConDevCorp.OpenArch.SmartData.Core.DisplayFormatters;
using DynConDevCorp.OpenArch.SmartData.Interfaces;
using DynConDevCorp.OpenArch.SmartData.Interfaces.EventArgClasses;

#endregion

namespace DynConDevCorp.OpenArch.SmartData.Core
{
    public class SmartNullableField<TData> : SmartField, ISmartNullableField<TData>
        where TData : struct, IComparable<TData>
    {
        public override void Dispatch(ISmartDispatcher dispatcher, object context)
        {
            dispatcher.ProcessDispatch(this, context);
        }

        #region ISmartNullableField<TData> Members

        /// <summary>
        /// Adds the effect.
        /// </summary>
        /// <param name="effect">The effect.</param>
        public void AddEffect(ISmartEffect<TData?> effect)
        {
            m_Effects.Add(effect);
            ValueChangedEventArgs<TData?> args = new ValueChangedEventArgs<TData?>(this, default(TData?), Value);
            effect.InvokeEffect(null, args);
        }

        /// <summary>
        /// Adds the rule.
        /// </summary>
        /// <param name="rule">The rule.</param>
        public void AddRule(ISmartRule<TData?> rule)
        {
            m_Rules.Add(rule);
        }

        /// <summary>
        /// Formatteds the display.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <returns></returns>
        public override string FormattedDisplay(string format)
        {
            return Value.ToString();
        }

        /// <summary>
        /// Removes the effect.
        /// </summary>
        /// <param name="effect">The effect.</param>
        public void RemoveEffect(ISmartEffect<TData?> effect)
        {
            m_Effects.Remove(effect);
        }

        /// <summary>
        /// Removes the rule.
        /// </summary>
        /// <param name="rule">The rule.</param>
        public void RemoveRule(ISmartRule<TData?> rule)
        {
            m_Rules.Remove(rule);
        }

        /// <summary>
        /// Rollback value to last Committed Value.
        /// </summary>
        public override void Rollback()
        {
            if (TransactionMode == TrackingMode.Dirty)
            {
                Value = m_PreviousValue;
                TransactionMode = TrackingMode.Committed;
            }
        }


        /// <summary>
        /// Occurs when [value changed].
        /// </summary>
        public event EventHandler<ValueChangedEventArgs<TData?>> ValueChanged
        {
            add
            {
                ISmartEffect<TData?> effect = new SmartEffect<TData?>(value);
                AddEffect(effect);
            }
            remove
            {
                ISmartEffect<TData?> effect = new SmartEffect<TData?>(value);
                RemoveEffect(effect);
            }
        }

        #endregion

        #region Public Instance Properties

        /// <summary>
        /// Gets the type of the data.
        /// </summary>
        /// <value>The type of the data.</value>
        public override Type DataType
        {
            get { return typeof (TData?); }
        }

        /// <summary>
        /// Gets or sets the display formatter.
        /// </summary>
        /// <value>The display formatter.</value>
        public ISmartDisplayFormatter<TData> DisplayFormatter { get; set; }

        /// <summary>
        /// Gets the display value.
        /// </summary>
        /// <value>The display value.</value>
        public override string DisplayValue
        {
            get
            {
                ISmartDisplayFormatter formatter = DisplayFormatterFactory.GlobalInstance.GetDefaultFormatter<TData?>();
                return formatter.Format(Value, DisplayFormatString);
            }
        }

        /// <summary>
        /// Gets or sets the input parser.
        /// </summary>
        /// <value>The input parser.</value>
        public ISmartInputParser<TData?> InputParser { get; set; }

        /// <summary>
        /// Sets the input value.
        /// </summary>
        /// <value>The input value.</value>
        public override string InputValue
        {
            set
            {
                TData? input = (TData?) Convert.ChangeType(value, typeof (TData?), CultureInfo.InvariantCulture);
                Value = input;
            }
        }

        /// <summary>
        /// Gets or sets the raw value.
        /// </summary>
        /// <value>The raw value.</value>
        public override object RawValue
        {
            get { return Value; }
            set
            {
                if (value != null)
                {
                    TData typedValue = (TData) Convert.ChangeType(value, typeof (TData), CultureInfo.InvariantCulture);
                    Value = typedValue;
                }
                else
                {
                    Value = null;
                }
            }
        }

        /// <summary>
        /// Gets or sets the value.
        /// </summary>
        /// <value>The value.</value>
        public TData? Value
        {
            get { return m_Value; }
            set
            {
                // ReSharper disable CompareNonConstrainedGenericWithNull
                if (m_Value != null)
                {
                    if (m_Value.Equals(value)) return;
                }
                else
                {
                    if (value == null) return;
                }
                // ReSharper restore CompareNonConstrainedGenericWithNull
                if (!EvaluateRules(value))
                    throw new ReadOnlyException("SmartField has WriteLocked Property Set");

                if (TransactionMode == TrackingMode.Committed)
                {
                    m_PreviousValue = m_Value;
                    TransactionMode = TrackingMode.Dirty;
                }
                TData? oldValue = m_Value;
                m_Value = value;
                ProcessValueChanged(oldValue, m_Value);
            }
        }

        #endregion

        #region Protected Instance Methods

        /// <summary>
        /// Updates the read only rule.
        /// </summary>
        protected override void UpdateWriteLockedRule()
        {
            if ((WriteLocked == PartialBoolean.True) && (m_ReadOnlyRule == null))
            {
                m_ReadOnlyRule = new SmartRule<TData?>();
                AddRule(m_ReadOnlyRule);
            }

            if ((WriteLocked != PartialBoolean.True) && (m_ReadOnlyRule != null))
            {
                RemoveRule(m_ReadOnlyRule);
                m_ReadOnlyRule = null;
            }
        }

        #endregion

        #region Private Instance Methods

        /// <summary>
        /// Evaluates the rules.
        /// </summary>
        /// <param name="proposedValue">The proposed value.</param>
        /// <returns></returns>
        private bool EvaluateRules(TData? proposedValue)
        {
            foreach (ISmartRule<TData?> rule in m_Rules)
            {
                if (!rule.Evaluate(this, proposedValue))
                    return false;
            }
            return true;
        }

        private void InvokeValueChanged(TData? oldValue, TData? newValue)
        {
            ValueChangedEventArgs<TData?> args = new ValueChangedEventArgs<TData?>(this, oldValue, newValue);
            foreach (ISmartEffect<TData?> effect in m_Effects)
            {
                effect.InvokeEffect(this, args);
            }
            if (OwningEntity != null)
            {
                OwningEntity.PropogateFieldEffects(this, args);
            }
        }

        private void ProcessValueChanged(TData? oldValue, TData? newValue)
        {
            InvokeValueChanged(oldValue, newValue);
            InvokeRawValueChanged(oldValue, m_Value);
            InvokeDisplayValueChanged(DisplayValue);
        }

        #endregion

        #region Fields and Constants

        private readonly List<ISmartEffect<TData?>> m_Effects = new List<ISmartEffect<TData?>>();
        private readonly List<ISmartRule<TData?>> m_Rules = new List<ISmartRule<TData?>>();
        private TData? m_PreviousValue;
        private ISmartRule<TData?> m_ReadOnlyRule;
        private TData? m_Value;

        #endregion
    }
}